/*-
 * Copyright (c) 2009 Jonathan Anderson
 * All rights reserved.
 *
 * WARNING: THIS IS EXPERIMENTAL SECURITY SOFTWARE THAT MUST NOT BE RELIED
 * ON IN PRODUCTION SYSTEMS.  IT WILL BREAK YOUR SOFTWARE IN NEW AND
 * UNEXPECTED WAYS.
 * 
 * This software was developed at the University of Cambridge Computer
 * Laboratory with support from a grant from Google, Inc. 
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/capability.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <libcapsicum.h>

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>

#include "libuserangel.h"


typedef struct ua_datum datum;

void handle_error(const datum *d);


int angel = -1;

char client_name[81] = "libuserangel";

const char *UA_PROTOCOL_VERSION = "UA/0.1";
const char *ua_protocol_version() { return UA_PROTOCOL_VERSION; }


// note: the cmsghdr is aligned to a word boundary
size_t CMSGHDR_SIZE =
	sizeof(struct cmsghdr) + (sizeof(struct cmsghdr) % sizeof(long));



int ua_set_client_name(const char *name)
{
	strncpy(client_name, name, 80);
	if(strnlen(name, 81) > 80)
	{
		errno = EOVERFLOW;
		return -1;
	}
	else return 0;
}


int ua_find(void)
{
	char *homedir = getenv("HOME");

	if(strlen(homedir) > 200)
	{
		errno = ENAMETOOLONG;
		return -1;
	}

	char control_socket_name[256] = "";

	sprintf(control_socket_name, "%s/.user-angel", homedir);

	struct sockaddr_un addr;
	addr.sun_family = AF_UNIX;
	strcpy(addr.sun_path, control_socket_name);

	angel = socket(AF_UNIX, SOCK_STREAM, 0);
	if(connect(angel, (struct sockaddr*) &addr, sizeof(addr))) return -1;

	int on = 1;
	if(setsockopt(angel, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on))) return -1;
	if(lc_limitfd(angel, CAP_READ | CAP_WRITE) < 0) return -1;


	// receive server 'hello'
	struct ua_datum *d = ua_recv(angel, NULL, NULL);
	if(!d)
	{
		int olderrno = errno;
		close(angel);
		errno = olderrno;
		angel = -1;
		return -1;
	}

	size_t hellolen = 120;
	char hello[hellolen];
	if(ua_unmarshall_string(d, hello, &hellolen) < 0)
	{
		int olderrno = errno;
		close(angel);
		errno = olderrno;
		angel = -1;
		return -1;
	}
	free(d);

	// validate server 'hello' message
	if(strncmp(hello, "user_angel", 10))
	{
		close(angel);
		errno = EFTYPE;
		angel = -1;
		return -1;
	}

	char *version = hello;
	while(*version != ' ') version++;
	version++;

	size_t len = strlen(version);
	if((len != strlen(UA_PROTOCOL_VERSION))
	   || strncmp(version, UA_PROTOCOL_VERSION, len))
	{
		close(angel);
		errno = ERPCMISMATCH;
		angel = -1;
		return -1;
	}


	// send client name
	d = ua_marshall_string(client_name, strnlen(client_name, 80));
	if(!d)
	{
		int olderrno = errno;
		close(angel);
		errno = olderrno;
		angel = -1;
		return -1;
	}

	if(ua_send(angel, d, NULL, 0) < 0)
	{
		int olderrno = errno;
		free(d);
		close(angel);
		errno = olderrno;
		angel = -1;
		return -1;
	}

	free(d);


	return angel;
}



void ua_set(int fd) { angel = fd; }



int ua_ping()
{
	if(angel < 0) angel = ua_find();
	if(angel < 0) return -1;

	datum *d = ua_marshall_int(UA_NO_OP);
	if(ua_send(angel, d, NULL, 0) < 0) return -1;
	if(ua_send(angel, d, NULL, 0) < 0) return -1;  /* we have to send an arg */
	free(d);

	d = ua_recv(angel, NULL, NULL);
	if(!d) return -1;

	int response;
	if(ua_unmarshall_int(d, &response) < 0) return -1;

	return response;
}



int ua_cache_fd(int fd, const char *name, char **token, int long_lasting)
{
	if(angel < 0) angel = ua_find();
	if(angel < 0) return -1;

	struct ua_datum *data[4];
	data[0] = ua_marshall_int(UA_CACHE_FD);
	data[1] = ua_marshall_int(1);
	data[2] = ua_marshall_int(long_lasting);
	data[3] = ua_marshall_string(name, strlen(name));

	for(int i = 0; i < 3; i++)
	{
		if(ua_send(angel, data[i], NULL, 0) < 0) return -1;
		free(data[i]);
	}

	if(ua_send(angel, data[3], &fd, 1) < 0) return -1;
	free(data[3]);



	// retrieve the crypto token
	struct ua_datum *d = ua_recv(angel, NULL, NULL);
	if(!d) return -1;

	size_t len = d->length + 1;
	*token = malloc(len);
	if(ua_unmarshall_string(d, *token, &len) < 0) return -1;

	return 0;
}


int ua_retrieve_fd(const char *token)
{
	if(angel < 0) angel = ua_find();
	if(angel < 0) return -1;

	struct ua_datum *data[2];
	data[0] = ua_marshall_int(UA_RETRIEVE_FD);
	data[1] = ua_marshall_string(token, strlen(token));

	for(int i = 0; i < 2; i++)
	{
		if(ua_send(angel, data[i], NULL, 0) < 0) return -1;
		free(data[i]);
	}

	// retrieve the file descriptor
	int fd = -1;
	size_t fdlen = 1;
	struct ua_datum *d = ua_recv(angel, &fd, &fdlen);
	if(!d) return -1;

	// make sure there hasn't been an error
	size_t buflen = d->length + 1;
	char buf[buflen];
	if(ua_unmarshall_string(d, buf, &buflen) < 0) return -1;

	return fd;
}



int ua_access(const char *path, int mode)
{
	if(angel < 0) angel = ua_find();
	if(angel < 0) return -1;

	struct ua_datum *data[3];
	data[0] = ua_marshall_int(UA_CHECK_ACCESS);
	data[1] = ua_marshall_string(path, strlen(path));
	data[2] = ua_marshall_int(mode);

	for(int i = 0; i < 3; i++) if(ua_send(angel, data[i], NULL, 0) < 0) return -1;

	free(data[0]);
	free(data[1]);
	free(data[2]);



	// retrieve the file descriptor(s)
	struct ua_datum *d = ua_recv(angel, NULL, NULL);
	if(!d) return -1;

	int response;
	if(ua_unmarshall_int(d, &response) < 0) return -1;

	return response;
}



int ua_stat(const char *path, struct stat *s)
{
	if(angel < 0) angel = ua_find();
	if(angel < 0) return -1;

	struct ua_datum *data[2];
	data[0] = ua_marshall_int(UA_STAT);
	data[1] = ua_marshall_string(path, strlen(path));

	for(int i = 0; i < 2; i++)
		if(ua_send(angel, data[i], NULL, 0) < 0)
			return -1;

	free(data[0]);
	free(data[1]);



	struct ua_datum *d = ua_recv(angel, NULL, NULL);
	if(!d) return -1;

	size_t len = sizeof(struct stat);
	if(ua_unmarshall_bytes(d, (char*) s, &len) < 0) return -1;

	return 0;
}



int ua_open(const char *path, int flags, int umask, char **nameout, size_t *namelen)
{
	cap_rights_t rights = CAP_FSTAT | CAP_SEEK;

	if(flags & O_WRONLY)
		rights |= CAP_WRITE | CAP_FTRUNCATE | CAP_FSYNC;

	else if(flags & O_RDWR)
		rights |= CAP_READ | CAP_WRITE | CAP_FTRUNCATE | CAP_FSYNC;

	else rights |= CAP_READ;


	if(flags & O_DIRECTORY) rights |= CAP_FSTATFS | CAP_FEXECVE;

	return ua_ropen(path, flags, rights, umask, nameout, namelen);
}



int ua_ropen(const char *path, int flags, cap_rights_t rights, int mask,
             char **nameout, size_t *namelen)
{
	if(angel < 0) angel = ua_find();
	if(angel < 0) return -1;

	struct ua_datum *data[5];
	data[0] = ua_marshall_int(UA_OPEN_PATH);
	data[1] = ua_marshall_string(path, strlen(path));
	data[2] = ua_marshall_int(flags);
	data[3] = ua_marshall_long(rights);
	data[4] = ua_marshall_int(mask);

	for(int i = 0; i < 5; i++)
	{
		if(ua_send(angel, data[i], NULL, 0) < 0) return -1;
		free(data[i]);
	}



	// retrieve the file descriptor(s)
	struct ua_datum *fdcountd = ua_recv(angel, NULL, NULL);
	if(!fdcountd) return -1;

	int fdcount;
	if(ua_unmarshall_int(fdcountd, &fdcount) < 0) return -1;

	if(fdcount > 1)
	{
		errno = EOVERFLOW;
		return -1;
	}

	int fd;
	size_t fdlen = 1;
	struct ua_datum *fd_datum = ua_recv(angel, &fd, &fdlen);
	if(!fd_datum) return -1;

	if((nameout == NULL) ^ (namelen == NULL))
	{
		errno = EINVAL;
		return -1;
	}

	if(nameout)
	{
		*namelen = fd_datum->length + 1;
		*nameout = malloc(*namelen);
	}

	if(ua_unmarshall_string(fd_datum, *nameout, namelen) < 0) return -1;

	return fd;
}



FILE* ua_fopen(const char *path, const char *mode, char **nameout, size_t *namelen)
{
	if(angel < 0) angel = ua_find();
	if(angel < 0) return NULL;

	int flags = 0;
	cap_rights_t rights = CAP_SEEK | CAP_FSTAT | CAP_FCNTL;

	if(strnstr(mode, "r+", 3) || strnstr(mode, "rb+", 4))
	{
		flags	|= O_RDWR;
		rights	|= CAP_READ | CAP_WRITE | CAP_FTRUNCATE;
	}
	else if(strnstr(mode, "r", 2) || strnstr(mode, "rb", 3))
	{
		flags	|= O_RDONLY;
		rights	|= CAP_READ;
	}
	else if(strnstr(mode, "w+", 3) || strnstr(mode, "wb+", 4))
	{
		flags	|= O_RDWR | O_CREAT | O_TRUNC;
		rights	|= CAP_READ | CAP_WRITE | CAP_FTRUNCATE | CAP_FSYNC;
	}
	else if(strnstr(mode, "w", 2) || strnstr(mode, "wb", 3))
	{
		flags	|= O_WRONLY | O_CREAT | O_TRUNC;
		rights	|= CAP_WRITE | CAP_FTRUNCATE | CAP_FSYNC;
	}
	else if(strnstr(mode, "a+", 3) || strnstr(mode, "ab+", 4))
	{
		flags	|= O_RDWR | O_CREAT | O_APPEND;
		rights	|= CAP_READ | CAP_WRITE | CAP_FTRUNCATE | CAP_FSYNC;
	}
	else if(strnstr(mode, "a", 2) || strnstr(mode, "ab", 3))
	{
		flags	|= O_WRONLY | O_CREAT | O_APPEND;
		rights	|= CAP_WRITE | CAP_FTRUNCATE | CAP_FSYNC;
	}

	int fd = ua_ropen(path, flags, rights, 0, nameout, namelen);
	if(flags & O_APPEND)
		if(lseek(fd, 0, SEEK_END) < 0)
		{
			int olderrno = errno;
			close(fd);
			errno = olderrno;
			return NULL;
		}

	return fdopen(fd, mode);
}


DIR* ua_opendir(const char *filename, char **nameout, size_t *namelen)
{
	int fd = ua_open(filename, O_RDONLY | O_DIRECTORY, 0, nameout, namelen);
	if(fd < 0) return NULL;

	return fdopendir(fd);
}


int ua_powerbox(struct ua_powerbox_options *options,
                int fds[], char *names[], size_t *len)
{
	if(angel < 0) angel = ua_find();
	if(angel < 0) return -1;


	struct ua_datum *data[2];
	data[0] = ua_marshall_int(UA_POWERBOX);
	data[1] = ua_marshall_powerbox(options);
	
	for(int i = 0; i < 2; i++)
	{
		if(ua_send(angel, data[i], NULL, 0) < 0) return -1;
		free(data[i]);
	}
	

	// retrieve the file descriptor(s)
	struct ua_datum *fdcountd = ua_recv(angel, NULL, NULL);
	if(!fdcountd) return -1;

	int32_t len32 = 0;
	if(ua_unmarshall_int(fdcountd, &len32) < 0) return -1;
	*len = len32;

	if(*len == 0)
	{
		errno = ECANCELED;
		return 0;
	}

	int buflen = 1024;
	char buffer[buflen];
	for(int i = 0; i < *len; i++)
	{
		int fd;
		size_t count = 1;

		struct ua_datum *fd_datum = ua_recv(angel, &fd, &count);
		if(!fd_datum) return -1;

		fds[i] = fd;

		size_t namelen = buflen;
		if(ua_unmarshall_string(fd_datum, buffer, &namelen) < 0) return -1;

		names[i] = (char*) malloc(namelen);
		memcpy(names[i], buffer, namelen);
		(names[i])[namelen] = '\0';
	}
	
	return 0;
}



int ua_send(int sock, datum *d, int fds[], size_t fdlen)
{
	// the datum is the I/O vector
	struct iovec iov;
	iov.iov_base = d;
	iov.iov_len = sizeof(datum) + d->length;


	// ancilliary data (file descriptors)
	struct cmsghdr *anc_hdr = NULL;
	if(fdlen > 0)
	{
		int len = CMSGHDR_SIZE + fdlen * sizeof(int);
		anc_hdr = (struct cmsghdr*) malloc(len);
		if(!anc_hdr) return -1;

		anc_hdr->cmsg_len = len;
		anc_hdr->cmsg_level = SOL_SOCKET;
		anc_hdr->cmsg_type = (fdlen ? SCM_RIGHTS : 0);

		char *data = ((char*) anc_hdr) + CMSGHDR_SIZE;
		memcpy(data, fds, fdlen * sizeof(int));
	}


	// sendmsg header
	struct msghdr header;
	header.msg_name = NULL;
	header.msg_namelen = 0;
	header.msg_iov = &iov;
	header.msg_iovlen = 1;
	header.msg_control = anc_hdr;
	header.msg_controllen = ((fdlen > 0) ? anc_hdr->cmsg_len : 0);
	header.msg_flags = 0;


	// send!
	int bytes_sent = sendmsg(sock, &header, 0);
	free(anc_hdr);

	if(bytes_sent < 0) return -1;


	return bytes_sent;
}


struct ua_datum* ua_recv(int sock, int fds[], size_t *fdlen)
{
	// how much data is there to receive?
	datum peek;
	int bytes = recv(sock, &peek, sizeof(datum), MSG_PEEK); 
	if(bytes <= 0) return NULL;

	int to_receive = sizeof(datum) + peek.length;


	// make room for it
	datum *d = (datum*) malloc(to_receive);
	struct iovec iov;
	iov.iov_base = d;
	iov.iov_len = to_receive;


	// prepare to receive file descriptor(s)
	int maxfds = (fdlen ? *fdlen : 0);
	int size = CMSGHDR_SIZE + maxfds * sizeof(int);
	struct cmsghdr *anc_hdr = (struct cmsghdr*) malloc(size);
	if(!anc_hdr) return NULL;

	bzero(anc_hdr, size);
	anc_hdr->cmsg_len = size;
	anc_hdr->cmsg_level = SOL_SOCKET;
	anc_hdr->cmsg_type = (maxfds ? SCM_RIGHTS : 0);

	// recvmsg() options
	struct msghdr header;
	header.msg_name = NULL;
	header.msg_namelen = 0;
	header.msg_iov = &iov;
	header.msg_iovlen = 1;
	header.msg_control = anc_hdr;
	header.msg_controllen = anc_hdr->cmsg_len;
	header.msg_flags = 0;


	bytes = recvmsg(sock, &header, MSG_WAITALL);
	if(bytes <= 0)
	{
		int olderrno = errno;
		free(anc_hdr);
		free(d);
		errno = olderrno;
		return NULL;
	}


	if(maxfds > 0)
	{
		char *data = ((char*) anc_hdr) + CMSGHDR_SIZE;
		size_t datalen = anc_hdr->cmsg_len - CMSGHDR_SIZE;
		size_t recv_fdlen = datalen / sizeof(int);

		if(recv_fdlen < *fdlen) *fdlen = recv_fdlen;

		memcpy(fds, data, datalen);
	}

	free(anc_hdr);

	return d;
}




datum* ua_marshall_int(int32_t value)
{
	int size = sizeof(datum) + sizeof(int32_t);
	datum *d = (datum*) malloc(size);
	
	d->type = INTEGER;
	d->length = sizeof(int32_t);

	void *address = ((char*) d) + sizeof(datum);
	((int32_t*) address)[0] = value;

	return d;
}



int ua_unmarshall_int(const datum *d, int32_t *value)
{
	if(d == NULL)
	{
		errno = EINVAL;
		return -1;
	}

	if(!(d->type & INTEGER))
	{
		if(d->type & ERROR) handle_error(d);
		else errno = EINVAL;
		return -1;
	}

	if(d->length > sizeof(int32_t))
	{
		errno = EOVERFLOW;
		return -1;
	}
	else bzero(value, sizeof(int32_t));

	memcpy(value, ((const char*) d) + sizeof(datum), sizeof(int32_t));
	return d->length;
}




datum* ua_marshall_long(int64_t value)
{
	int size = sizeof(datum) + sizeof(value);
	datum *d = (datum*) malloc(size);
	
	d->type = LONG | INTEGER;
	d->length = sizeof(value);

	void *address = ((char*) d) + sizeof(datum);
	((int64_t*) address)[0] = value;

	return d;
}



int ua_unmarshall_long(const datum *d, int64_t *value)
{
	if(d == NULL)
	{
		errno = EINVAL;
		return -1;
	}

	if(!(d->type & (LONG | INTEGER)))
	{
		if(d->type & ERROR) handle_error(d);
		else errno = EINVAL;
		return -1;
	}

	if(d->length > sizeof(int64_t))
	{
		errno = EOVERFLOW;
		return -1;
	}
	else bzero(value, sizeof(int32_t));

	memcpy(value, ((const char*) d) + sizeof(datum), sizeof(*value));

	return d->length;
}




datum* ua_marshall_bytes(const char *value, size_t len)
{
	int size = sizeof(datum) + len;
	datum *d = (datum*) malloc(size);

	d->type = STRING;
	d->length = len;
	memcpy(((char*) d) + sizeof(datum), value, len);

	return d;
}


datum* ua_marshall_string(const char *value, size_t len)
{
	return ua_marshall_bytes(value, len);
}


int ua_unmarshall_string(const datum *d, char *value, size_t *len)
{
	(*len)--;

	int ret = ua_unmarshall_bytes(d, value, len);
	if(ret < 0) return ret;

	value[*len] = '\0';

	return d->length;
}


int ua_unmarshall_bytes(const datum *d, char *value, size_t *len)
{
	if(d == NULL)
	{
		errno = EINVAL;
		return -1;
	}
	else if(d->type != STRING)
	{
		if(d->type & ERROR) handle_error(d);
		else errno = EINVAL;
		return -1;
	}
	else if(d->length > *len)
	{
		errno = EOVERFLOW;
		return -1;
	}

	*len = d->length;
	memcpy(value, ((const char*) d) + sizeof(datum), d->length);

	return d->length;
}




datum* ua_marshall_error(int errnum, const char *msg, size_t msglen)
{
	size_t size = sizeof(datum) + sizeof(int32_t) + msglen;
	datum *d = (datum*) malloc(size);
	
	d->type = ERROR;
	d->length = sizeof(int32_t) + msglen;

	char *address = ((char*) d) + sizeof(datum);
	*((int32_t*) address) = errnum;

	address += sizeof(int32_t);
	memcpy((char*) address, msg, msglen);

	return d;
}


int ua_unmarshall_error(const datum *d, int *errnum, char *msg, int *msglen)
{
	if(d == NULL)
	{
		errno = EINVAL;
		return -1;
	}
	else if(d->type != ERROR)
	{
		errno = EINVAL;
		return -1;
	}
	else if(d->length >= (*msglen - sizeof(int32_t)))
	{
		errno = EOVERFLOW;
		return -1;
	}

	const char *address = ((const char*) d) + sizeof(datum);
	*errnum = *((const int32_t*) address);
	address += sizeof(int32_t);

	*msglen = d->length - sizeof(int32_t);
	memcpy(msg, address, *msglen);
	msg[*msglen] = '\0';

	return d->length;
}



datum* ua_marshall_powerbox(const struct ua_powerbox_options *options)
{
	datum *data[9];
	data[0] = ua_marshall_int(options->ui);
	data[1] = ua_marshall_int(options->operation);
	data[2] = ua_marshall_int(options->parent_window);
	data[3] = ua_marshall_string(options->start_path, options->pathlen);
	data[4] = ua_marshall_int(options->mult);
	data[5] = ua_marshall_string(options->filter, options->filterlen);
	data[6] = ua_marshall_int(options->flags);
	data[7] = ua_marshall_long(options->rights);
	data[8] = ua_marshall_int(options->umask);

	int total_size = 0;
	for(int i = 0; i <= 8; i++)
		if(data[i] == NULL)
		{
			errno = EINVAL;
			return NULL;
		}
		else total_size += (sizeof(datum) + data[i]->length);

	datum *d = (datum*) malloc(sizeof(datum) + total_size);
	d->type = CAPBOX_OPTIONS;
	d->length = total_size;

	char *buffer = ((char*) d) + sizeof(datum);
	char *head = buffer;
	for(int i = 0; i <= 8; i++)
	{
		memcpy(head, data[i], sizeof(datum) + data[i]->length);
		head += sizeof(datum) + data[i]->length;

		free(data[i]);
	}

	return d;
}


int ua_unmarshall_powerbox(const datum *d, struct ua_powerbox_options *options)
{
	if(d == NULL)
	{
		errno = EINVAL;
		return -1;
	}
	else if(d->type != CAPBOX_OPTIONS)
	{
		if(d->type & ERROR) handle_error(d);
		else errno = EINVAL;
		return -1;
	}

	int32_t tmp_int;
	const datum *head = (const datum*) (((const char*) d) + sizeof(datum));
	if(ua_unmarshall_int(head, &tmp_int) < 0) return -1;

	options->ui = tmp_int;
	head = (const datum*) (((const char*) head) + sizeof(datum) + head->length);
	if(ua_unmarshall_int(head, &tmp_int) < 0) return -1;

	options->operation = tmp_int;
	head = (const datum*) (((const char*) head) + sizeof(datum) + head->length);


	// window title is handled elsewhere


	if(ua_unmarshall_int(head, &tmp_int) < 0) return -1;
	options->parent_window = tmp_int;
	head = (const datum*) (((const char*) head) + sizeof(datum) + head->length);

	options->pathlen = d->length + 1;
	char *str = (char*) malloc(options->pathlen);
	if(ua_unmarshall_string(head, str, &options->pathlen) < 0) return -1;
	options->start_path = str;
	head = (const datum*) (((const char*) head) + sizeof(datum) + head->length);


	// don't do anything about the start_fd file descriptor;
	// that's handled at the recvmsg() level


	if(ua_unmarshall_int(head, &tmp_int) < 0) return -1;
	options->mult = tmp_int;
	head = (const datum*) (((const char*) head) + sizeof(datum) + head->length);

	options->filterlen = d->length + 1;
	str = (char*) malloc(options->filterlen);
	if(ua_unmarshall_string(head, str, &options->filterlen) < 0) return -1;
	options->filter = str;
	head = (const datum*) (((const char*) head) + sizeof(datum) + head->length);

	if(ua_unmarshall_int(head, &tmp_int) < 0) return -1;
	options->flags = tmp_int;
	head = (const datum*) (((const char*) head) + sizeof(datum) + head->length);

	int64_t tmp_long;
	if(ua_unmarshall_long(head, &tmp_long) < 0) return -1;
	options->rights = tmp_long;
	head = (const datum*) (((const char*) head) + sizeof(datum) + head->length);

	if(ua_unmarshall_int(head, &tmp_int) < 0) return -1;
	options->umask = tmp_int;


	return sizeof(datum) + d->length;
}


typedef void (*error_handler)(const char*);

void default_error_handler(const char *message)
{
	fprintf(stderr, "libuserangel error: %s\n", message);
}

error_handler current_error_handler = default_error_handler;


void handle_error(const datum *d)
{
	int32_t errnum;
	char message[200];
	int msglen = 200;

	if(ua_unmarshall_error(d, &errnum, message, &msglen) < 0) return;

	current_error_handler(message);
}

